﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


namespace Perlin_Interactive_Demo_1
{
    static class PerlinNoise
    {
        static bool isInitalized = false;
        const int BASE_DATA_DIM = 8;
        static Vector3[, ,] basedata = new Vector3[BASE_DATA_DIM, BASE_DATA_DIM, BASE_DATA_DIM];

        public static void Generate(ref Texture2D texture, uint octaves, float phase)
        {
            if (!isInitalized)
                InitalizeNoise();

            if (octaves == 0)
                octaves = 1;

            Color[] buffer = new Color[texture.Width * texture.Height];
            texture.GetData<Color>(buffer);
            float pixel = 0f;

            for (int x = 0; x < texture.Width; x++)
            {
                float xCoord = (float)x / (float)texture.Width;

                for (int y = 0; y < texture.Height; y++)
                {
                    float yCoord = (float)y / (float)texture.Height;

                    pixel = 1f;

                    for (int o = 0, res = 1; o < octaves; o++, res *= 2)
                    {
                        float amp = (float)Math.Pow(2.0, (double)o);
                        pixel += Noise(xCoord, yCoord, phase) * (1f / amp);

                    }

                    pixel = Math.Min(1f, Math.Max(0f, pixel * 0.5f));
                    buffer[x + (y * texture.Width)] = new Color(pixel, pixel, pixel);
                }
            }

            texture.SetData<Color>(buffer);
        }

        static Vector3 QueryBase(int x, int y, int z)
        {
            int xi = (x % BASE_DATA_DIM);
            int yi = (y % BASE_DATA_DIM);
            int zi = (z % BASE_DATA_DIM);

            return basedata[xi, yi, zi];
        }

        static void InitalizeNoise()
        {
            for (int x = 0; x < BASE_DATA_DIM; x++)
            {
                for (int y = 0; y < BASE_DATA_DIM; y++)
                {
                    for (int z = 0; z < BASE_DATA_DIM; z++)
                    {
                        Vector3 v = new Vector3(Util.RandomFloat(-1f, 1f), Util.RandomFloat(-1f, 1f), Util.RandomFloat(-1f, 1f));
                        Vector3.Normalize(v);
                        basedata[x, y, z] = v;
                    }
                }
            }
            isInitalized = true;
        }

        static float InterpolateCurve(float s)
        {
            s = 1f - s;
            return s * s * s * (s * (s * 6.0f - 15.0f) + 10.0f);
        }

        static float InterpolateLinear(float a, float b, float t)
        {
            return (a + (1f - t) * (b - a));
        }

        static float Noise(float xCoord, float yCoord, float zCoord)
        {
            float x = xCoord - (float)Math.Floor(xCoord);
            float y = yCoord - (float)Math.Floor(yCoord);
            float z = zCoord - (float)Math.Floor(zCoord);

            int q_leftc = (int)(x * BASE_DATA_DIM);
            int q_rightc = (q_leftc + 1);
            int q_topc = (int)(y * BASE_DATA_DIM);
            int q_bottomc = (q_topc + 1);
            int q_frontc = (int)(z * BASE_DATA_DIM);
            int q_backc = (q_frontc + 1);

            Vector3 q_tlf = QueryBase(q_leftc, q_topc, q_frontc);
            Vector3 q_trf = QueryBase(q_rightc, q_topc, q_frontc);
            Vector3 q_blf = QueryBase(q_leftc, q_bottomc, q_frontc);
            Vector3 q_brf = QueryBase(q_rightc, q_bottomc, q_frontc);
            Vector3 q_tlb = QueryBase(q_leftc, q_topc, q_backc);
            Vector3 q_trb = QueryBase(q_rightc, q_topc, q_backc);
            Vector3 q_blb = QueryBase(q_leftc, q_bottomc, q_backc);
            Vector3 q_brb = QueryBase(q_rightc, q_bottomc, q_backc);

            float q_leftf = (float)q_leftc / (float)BASE_DATA_DIM;
            float q_rightf = (float)q_rightc / (float)BASE_DATA_DIM;
            float q_topf = (float)q_topc / (float)BASE_DATA_DIM;
            float q_bottomf = (float)q_bottomc / (float)BASE_DATA_DIM;
            float q_frontf = (float)q_frontc / (float)BASE_DATA_DIM;
            float q_backf = (float)q_backc / (float)BASE_DATA_DIM;

            float t_leftx = x - q_leftf;
            float t_rightx = t_leftx - (1f / (float)BASE_DATA_DIM);
            float t_topy = y - q_topf;
            float t_bottomy = t_topy - (1f / (float)BASE_DATA_DIM);
            float t_frontz = z - q_frontf;
            float t_backz = t_frontz - (1f / (float)BASE_DATA_DIM);

            float v_tlf = (q_tlf.X * t_leftx + q_tlf.Y * t_topy + q_tlf.Z * t_frontz) * (float)BASE_DATA_DIM;
            float v_trf = (q_trf.X * t_rightx + q_trf.Y * t_topy + q_trf.Z * t_frontz) * (float)BASE_DATA_DIM;
            float v_blf = (q_blf.X * t_leftx + q_blf.Y * t_bottomy + q_blf.Z * t_frontz) * (float)BASE_DATA_DIM;
            float v_brf = (q_brf.X * t_rightx + q_brf.Y * t_bottomy + q_brf.Z * t_frontz) * (float)BASE_DATA_DIM;
            float v_tlb = (q_tlb.X * t_leftx + q_tlb.Y * t_topy + q_tlb.Z * t_backz) * (float)BASE_DATA_DIM;
            float v_trb = (q_trb.X * t_rightx + q_trb.Y * t_topy + q_trb.Z * t_backz) * (float)BASE_DATA_DIM;
            float v_blb = (q_blb.X * t_leftx + q_blb.Y * t_bottomy + q_blb.Z * t_backz) * (float)BASE_DATA_DIM;
            float v_brb = (q_brb.X * t_rightx + q_brb.Y * t_bottomy + q_brb.Z * t_backz) * (float)BASE_DATA_DIM;

            float icX = InterpolateCurve(t_leftx * BASE_DATA_DIM);
            float icY = InterpolateCurve(t_topy * BASE_DATA_DIM);
            float icZ = InterpolateCurve(t_frontz * BASE_DATA_DIM);

            float vx0 = InterpolateLinear(v_tlf, v_trf, icX);
            float vx1 = InterpolateLinear(v_blf, v_brf, icX);
            float vx2 = InterpolateLinear(v_tlb, v_trb, icX);
            float vx3 = InterpolateLinear(v_blb, v_brb, icX);

            float vy0 = InterpolateLinear(vx0, vx1, icY);
            float vy1 = InterpolateLinear(vx2, vx3, icY);

            float vf = InterpolateLinear(vy0, vy1, icZ);

            return vf;
        }
    }
}
